package com.csun.dsms.service.impl;

import com.csun.dsms.comment.core.support.BaseService;
import com.csun.dsms.mapper.DsmsScheduleMapper;
import com.csun.dsms.model.domain.DsmsSchedule;
import com.csun.dsms.model.dto.schedule.ScheduleQueryDto;
import com.csun.dsms.model.enums.ScheduleStatusEnum;
import com.csun.dsms.service.DsmsScheduleService;
import com.csun.dsms.task.ScheduledTaskJob;
import com.csun.dsms.utils.ApplicationContextUtil;
import com.csun.dsms.utils.PublicUtil;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author chisj chisj@foxmail.com
 * @version 1.0.0
 * @date 2021-09-23 15:35
 * @description: TODO
 */
@Slf4j
@Service
@Transactional
public class DsmsScheduleServiceImpl extends BaseService<DsmsSchedule> implements DsmsScheduleService {

    @Value("${task.enabled}")
    private Boolean taskEnable;

    @Resource
    private DsmsScheduleMapper dsmsScheduleMapper;

    /**
     * 定时任务线程池
     */
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    /**
     * 存放已经启动的任务map
     */
    private Map<String, ScheduledFuture> scheduledFutureMap = new ConcurrentHashMap<>();

    /**
     * 可重入锁
     */
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void init() {

        if (!taskEnable) {
            log.info("=====> disable dsms order schedule task...");
            return ;
        }

        DsmsSchedule dsmsSchedule = new DsmsSchedule();
        dsmsSchedule.setStatus(ScheduleStatusEnum.ENABLE.getType());
        List<DsmsSchedule> dsmsScheduleList = dsmsScheduleMapper.select(dsmsSchedule);
        if (PublicUtil.isEmpty(dsmsScheduleList)) {
            log.info("=====> not found schedule job...");
            return ;
        }
        for (DsmsSchedule job : dsmsScheduleList) {
            String scheduleCode = job.getScheduleCode();
            if (isRun(scheduleCode)) {
                restart(scheduleCode, job);
            } else {
                run(job);
            }
        }
        log.info("=====> schedule job init...");
    }

    @Override
    public boolean start(String scheduleCode, DsmsSchedule dsmsSchedule) {

        lock.lock();

        try {
            if (isRun(scheduleCode)) {
                return false;
            }
            if (PublicUtil.isEmpty(dsmsSchedule)) {
                dsmsSchedule = selectByScheduleCode(scheduleCode);
            }
            if (PublicUtil.isEmpty(dsmsSchedule)) {
                return false;
            }

            DsmsSchedule record = new DsmsSchedule();
            record.setId(dsmsSchedule.getId());
            record.setStatus(ScheduleStatusEnum.ENABLE.getType());
            dsmsScheduleMapper.updateByPrimaryKeySelective(record);

            run(dsmsSchedule);
        } catch (Exception e) {
            throw new RuntimeException("start schedule error = " + e.getMessage());
        } finally {
            lock.unlock();
        }

        return true;
    }

    @Override
    public boolean stop(String scheduleCode) {

        if (scheduledFutureMap.containsKey(scheduleCode)) {
            ScheduledFuture scheduledFuture = scheduledFutureMap.get(scheduleCode);
            boolean cancel = scheduledFuture.cancel(true);
            log.info("=====> stop schedule = {}, result = {}", scheduleCode, cancel);
            scheduledFutureMap.remove(scheduleCode);

            DsmsSchedule dsmsSchedule = selectByScheduleCode(scheduleCode);

            DsmsSchedule record = new DsmsSchedule();
            record.setId(dsmsSchedule.getId());
            record.setStatus(ScheduleStatusEnum.ENABLE.getType());
            dsmsScheduleMapper.updateByPrimaryKeySelective(record);

            return true;
        }
        return false;
    }

    @Override
    public boolean restart(String scheduleCode, DsmsSchedule dsmsSchedule) {

        stop(scheduleCode);

        return start(scheduleCode, dsmsSchedule);
    }

    @Override
    public DsmsSchedule selectByScheduleCode(String scheduleCode) {

        DsmsSchedule dsmsSchedule = new DsmsSchedule();
        dsmsSchedule.setScheduleCode(scheduleCode);

        return selectOne(dsmsSchedule);
    }

    /**
     * 查询调度器列表 - 分页
     *
     * @param scheduleQueryDto  the schedule query dto
     *
     * @return  the dsms schedule list.
     */
    @Override
    public List<DsmsSchedule> queryListWithPage(ScheduleQueryDto scheduleQueryDto) {

        PageHelper.startPage(scheduleQueryDto.getPageNum(), scheduleQueryDto.getPageSize());

        return dsmsScheduleMapper.queryListWithPage(scheduleQueryDto);
    }

    /**
     * 执行任务
     *
     * @param dsmsSchedule
     */
    private void run(DsmsSchedule dsmsSchedule) {

        if (PublicUtil.isEmpty(dsmsSchedule)) {
            return ;
        }

        String scheduleCode = dsmsSchedule.getScheduleCode();
        String cron = dsmsSchedule.getCron();
        ScheduledTaskJob scheduledTaskJob = (ScheduledTaskJob) ApplicationContextUtil.getBean(scheduleCode);
        scheduledTaskJob.setSchedule(dsmsSchedule);
        log.info("=====> schedule job {}, cron = {}", dsmsSchedule.getScheduleName(), cron);
        ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(scheduledTaskJob,
                        (TriggerContext triggerContext) -> new CronTrigger(cron).nextExecutionTime(triggerContext));

        scheduledFutureMap.put(scheduleCode, scheduledFuture);
    }

    /**
     * 判断任务是否在运行
     *
     * @param scheduleCode  the schedule code
     *
     * @return
     */
    private boolean isRun(String scheduleCode) {

        if (scheduledFutureMap.containsKey(scheduleCode)) {
            if (!scheduledFutureMap.get(scheduleCode).isCancelled()) {
                return true;
            }
        }
        return false;
    }
}
